/*********************************************************************************
 * Software License Agreement (BSD License)
 * Copyright (c) 2012, Carlos D. Correa (info@ngraph.org)
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of netzen nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************************/
#include <QImage>
#include <QColor>

#include "glheaders.h"

#include <assert.h>
#include <string.h>
#include <math.h>
#include "renderUtils.h"
#include "mouseUtils.h"

#include "viewcreators.h"
#include "ui/uiUtils.h"
#include "controller.h"
#include "scatterplot.h"
#include "propertyUtils.h"


float randomf(float a, float b) {
    return (b * (float) rand()/(float) RAND_MAX  + a);
}

/*****************************
SCATTERPLOT
*****************************/

ScatterPlot::ScatterPlot(const char *title, const char *typeS): View("ScatterPlot", title) {
    colors = 0;
    vertices = 0;
	typeScatter = typeS;
}

void ScatterPlot::setup() {
	setPropertyStringList("Data.X", "Default", typeScatter);
	setPropertyStringList("Data.Y", "Default", typeScatter);
	setPropertyStringList("Data.Size", "Default", typeScatter);
	setPropertyStringList("Data.Color", Controller::addVirtualPrefix(string(typeScatter)+".color"), "node");
	setPropertyStringList("Data.Opacity", "Default", typeScatter);
	setPropertyStringList("Data.Hovered", "Default", typeScatter, true);

		setPropertyBool("Grid.Draw Grid X", true);
		setPropertyBool("Grid.Draw Grid Y", true);

    setPropertyStringList("Data.Colormap", Controller::addVirtualPrefix(string(typeScatter)+".colormap"), "colormap");
    setPropertyInt("Data.PointSize", 1);
    setPropertyInt("Grid.Tics X", 1);
    setPropertyInt("Grid.Tics Y", 1);
		setPropertyBool("Grid.Logscale X", false);
		setPropertyBool("Grid.Logscale Y", false);
    setMargins(120, 40, 40, 80);

    //setPropertyInt("Font Size",10);
    //setPropertyStringList("Font", "Helvetica","font");
}


void ScatterPlot::createArrays(int n) {
    if(n!=numVertices || !vertices) {
        this->numVertices = n;

        if(vertices) delete vertices;
        vertices = new float[2*n];

        if(colors) delete colors;        
        colors = new float[4*n];

    }
}

void ScatterPlot::renderPoints() {
    glHint(GL_POINT_SMOOTH, GL_NICEST);
    glEnable(GL_POINT_SMOOTH);
    glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

    if( getPropertyString("Data.Size")!="Default"){
        //string strSizeProp = getPropertyString("size");// node size varies according to the mapping property

        int iDefaultSize = getPropertyInt("Data.PointSize");
        glPointSize(iDefaultSize);

        //float minv, maxv;
        //minv=maxv=0.0;
        //getMinMax(strSizeProp, minv, maxv);

        int k=0;
        ElementIterator *it = createIterator();
        for(it->begin();it->hasMoreElements();) {
            PropertyBag *element = it->next();
            assert(element);

            float fValue_size = graph->getAsFloat(element,  PROPERTY(getPropertyString("size")), 1.0,  true );
            fValue_size += 0.0001; //avoid assign 0 point size
            //normalized value of size mapping property
            glPointSize(getPropertyInt("Data.PointSize")*fValue_size);
            glBegin(GL_POINTS);
            {                
                glColor4f(fColor_front, fColor_front, fColor_front, 1.0);
                glColor4f(colors[4*k+0], colors[4*k+1], colors[4*k+2], colors[4*k+3]);                
                glVertex2f(vertices[2*k+0],vertices[2*k+1]);
            }
            glEnd();
            k++;
        }
		graph->destroyIterator(it);

    }
    else{ // draw all nodes at once
        glPointSize(getPropertyInt("Data.PointSize"));
        glColorPointer(4, GL_FLOAT, 0, colors);
        glVertexPointer(2, GL_FLOAT, 0, vertices);
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_COLOR_ARRAY);
        glDrawArrays(GL_POINTS, 0, numVertices);

    }


}

unsigned int ScatterPlot::numElements() {
    return graph->numNodes();
}
ElementIterator * ScatterPlot::createIterator() {
    return graph->createNodeIterator();
}

void ScatterPlot::updateColors() {
    string cmap = PROPERTY(getPropertyString("Data.Colormap"));
    string colorProperty = PROPERTY(getPropertyString("Data.Color"));
    string opacityProperty  = PROPERTY(getPropertyString("Data.Opacity"));
    hoveredNode = 0; //reset hover node
    float rgb[3];
    int k = 0;
		bool changedSelection = false;
    ElementIterator *it = createIterator();
		vector<PropertyBag*> selection;
		vector<PropertyBag*> unselection;
    for(it->begin();it->hasMoreElements();) {
        PropertyBag *element = it->next();
        assert(element);
        rgb[0] = rgb[1] = rgb[2] = fColor_front;//0.0;

        if(cmap!="Default" && colorProperty!="Default"){
            getAsColor(element, colorProperty, cmap, rgb);
        }

        float transparency = graph->getAsFloat(element, opacityProperty);
        float x, y;
        x = vertices[2*k+0];
        y = vertices[2*k+1];

				int selected = element->getPropertyInt("node.selected");
        if(dragMode==DRAG) {
            if(inSelection(x, y)) {
							selection.push_back(element);
            } else {
							if(selected==selectionMarker) {
								unselection.push_back(element);
							}
            }
        }
        if(selected>0) {
            getSelectionColor(selected, rgb);
        }
        float px = xpixels(1.0);
        float py = ypixels(1.0);
        float distx = x - mousex;
        float disty = y - mousey;
        float scale  = 1.0;
        if(distx*distx + disty*disty < 2.0*scale*(px*px + py*py)) {
            getSelectionColor(selectionMarker, rgb);
            transparency = 1.8;
            hoveredNode = element; //update hover node
        }

        colors[4*k+0] = rgb[0];
        colors[4*k+1] = rgb[1];
        colors[4*k+2] = rgb[2];
        colors[4*k+3] = transparency;
        k++;
    }
    graph->destroyIterator(it);
		selectElements(unselection, 0);
		selectElements(selection, selectionMarker);
}

void ScatterPlot::updateVertices() {
    string propertyX = getPropertyX();
    string propertyY = getPropertyY();
    int k = 0;

    ElementIterator *it = createIterator();
    for(it->begin();it->hasMoreElements();) {
        PropertyBag *element = it->next();
        float x = getX(element,propertyX);
        float y = getY(element,propertyY);
        vertices[2*k+0] = transformX(propertyX, x);
        vertices[2*k+1] = transformY(propertyY, y);
        k++;
    }
    graph->destroyIterator(it);
}

inline float log10(float x) {
	return log(x)/log(10);
}

float ScatterPlot::transformX(string propertyX, float x) {
	float xp = x;
	if(getPropertyBool("Grid.Logscale X")) {
		float minv, maxv;
		getMinMax(propertyX, minv, maxv);
		float v0, v1;
		getMinMaxLog(propertyX, v0, v1);
		x = minv + (maxv-minv)*x;
		//printf("Value[%f] between log %f %f\n", x, v0, v1);
		//printf("V[%f] --> %f\n", x, (log10(x)-log10(v0))/(log10(v1)-log10(v0)));
		return (log10(x)-log10(v0))/(log10(v1)-log10(v0));
	}
	return xp;
}

float ScatterPlot::transformY(string propertyY, float x) {
	float xp = x;
	if(getPropertyBool("Grid.Logscale Y")) {
		float minv, maxv;
		getMinMax(propertyY, minv, maxv);
		float v0, v1;
		getMinMaxLog(propertyY, v0, v1);
		x = minv + (maxv-minv)*x;
		//printf("Value[%f] between log %f %f\n", x, v0, v1);
		float y = (log10(x)-log10(v0))/(log10(v1)-log10(v0));
		return y;
	}
	return xp;
}

void ScatterPlot::drawXTics(string propertyX, int numTicsX){

	vector<float> values;
	vector<string> names;
	// Returns locations (in unit square) and labels of tics
	if(getPropertyBool("Grid.Logscale X")) {
		getLogTics(propertyX, numTicsX, values, names);
	} else {
		getTics(propertyX, numTicsX, values, names);
	}
	for(unsigned int i=0;i<values.size();i++) {
		float x = values[i];
		glBegin(GL_LINES);
		{
				glVertex2f(x, ypixels(-10.0));
				glVertex2f(x, ypixels(-5.0));
		}
		glEnd();
		if(propertyX!="Default") {
			string str = names[i];
            renderText(x, ypixels(-20), (char *) str.c_str(), "center");
        }
    }
		
	if(getPropertyBool("Grid.Draw Grid X")) {
		glColor4f(0.6,0.6,0.6,1.0);
		glLineStipple(1, 0xF0F0);
		glEnable(GL_LINE_STIPPLE);
		glBegin(GL_LINES);
		for(unsigned int i=0;i<values.size();i++) {
			float x = values[i];
			glVertex2f(x, 0);
			glVertex2d(x, 1);
		}
		glEnd();
		glDisable(GL_LINE_STIPPLE);
		glColor4f(0.0,0.0,0.0,1.0);
	}
}

void ScatterPlot::drawYTics(string propertyY, int numTicsY) {
	vector<float> values;
	vector<string> names;
	// Returns locations (in unit square) and labels of tics
	if(getPropertyBool("Grid.Logscale Y")) {
		getLogTics(propertyY, numTicsY, values, names);
	} else {
		getTics(propertyY, numTicsY, values, names);
	}
	for(unsigned int i=0;i<values.size();i++) {
			float y = values[i];
        glBegin(GL_LINES);
        {
            glVertex2f(xpixels(-5.0), 0.0 + (1.0)*y);
            glVertex2f(xpixels(-10.0), 0.0 + (1.0)*y);
        }
        glEnd();
        if(propertyY!="Default") {
					string str = names[i];
            renderText(xpixels(-10.0), y, (char* )  str.c_str(), "right");
        }
    }

	if(getPropertyBool("Grid.Draw Grid Y")) {
		glColor4f(0.6,0.6,0.6,1.0);
		glLineStipple(1, 0xF0F0);
		glEnable(GL_LINE_STIPPLE);
		glBegin(GL_LINES);
		for(unsigned int i=0;i<values.size();i++) {
			float y = values[i];
			glVertex2f(0,y);
			glVertex2d(1,y);
		}
		glEnd();
		glDisable(GL_LINE_STIPPLE);
		glColor4f(0.0,0.0,0.0,1.0);
	}
}
void ScatterPlot::renderHoveredPoint(PropertyBag *element) {
    glPushMatrix();
    glTranslatef(mousex,mousey,0);

    string props = getPropertyString("Data.Hovered");
		vector<string> properties = PropertyUtils::parseMultiProperty(props);
		vector<string> lines;
    float textboundsW = 0, textboundsH = 0;

    bool ifMoreThanOneLine = false;
    if(properties.size()>1){ ifMoreThanOneLine = true; }

    if(properties.size()>0) {
			for(unsigned int i=0;i<properties.size();i++) {
				string sValue = graph->getAsString(element, PROPERTY(properties[i]));
				string line = (ifMoreThanOneLine)? ( PROPERTY(properties[i]) + " : " + sValue ):(sValue);
				lines.push_back(line);
				float textw, texth;
				getTextBounds(textw, texth, (char*) line.c_str());
				textboundsW = max(textboundsW, textw);
				//if(lines.size()!=1){ // first line
						textboundsH += 5*pixy; // add gap only between lines.
				//}
				textboundsH += texth;// + 5*pixy;
			}
		}

		if((int) lines.size()>0) {
				float transparency = 0.9;
				float tw = (float) textboundsW+xpixels(5.0);
				float th = (float) textboundsH+ypixels(0.0);
				float dx = xpixels(5.0);
				float dy = ypixels(5.0);

				shadowRectangle(xpixels(-2.5)+1.0*dx, ypixels(- 2.5) + 1.0*dy , tw - 2.0*dx, th - 2.0*dy, 2.0*dx, 2.0*dy);
				glColor4f(0.0,0.0,0.0,0.25);
				glColor4f(0.96,0.96,0.96,1.0);
				roundRectangle(xpixels(-2.5) + dx, ypixels(-2.5) + dy, fmax(0,tw - 2.0*dx), fmax(0,th - 2.0*dy), dx, dy);
				glColor4f(0,0,0,0.25*transparency);
				roundRectangleLine(xpixels(-2.5) + dx, ypixels(-2.5) + dy, tw - 2.0*dx, th - 2.0*dy, dx, dy);

				glColor4f(1,1,1,0.9*transparency);
				glColor4f(0,0,0,0.9*transparency);
				float ty = 0.0;
				for(unsigned int k=0;k<lines.size();k++) {
						float textw, texth;
						getTextBounds(textw,texth,(char*) lines[k].c_str());
						renderText(0, ty, (char*) lines[k].c_str(), "left");
						ty += texth + 5.0*pixy;
				}
		}      

    glPopMatrix();
}

string ScatterPlot::getPropertyX() {
    return PROPERTY(getPropertyString("Data.X"));
}
string ScatterPlot::getPropertyY() {
    return PROPERTY(getPropertyString("Data.Y"));
}
float ScatterPlot::getX(PropertyBag *element, string property) {
	float x = graph->getAsFloat(element,property);
	return x;
}
float ScatterPlot::getY(PropertyBag *element, string property) {
	float y = graph->getAsFloat(element,property);
	return y;
}

void ScatterPlot::render() {
    string propertyX = getPropertyX();
    string propertyY = getPropertyY();

    int N = numElements();
    createArrays(N);
    assert(vertices);

    if(propertyX!=curPropertyX || propertyY!=curPropertyY) {
        curPropertyX = propertyX;
        curPropertyY = propertyY;
        updateVertices();
    }
    // Draw axes
    glLineWidth(1);
    //glColor4f(0,0,0,0.95);
    glColor4f(fColor_front,fColor_front,fColor_front,0.95);
    glBegin(GL_LINE_STRIP);
    {
        glVertex2f(0.0, ypixels(-5.0)); // bottom left
        glVertex2f(1.0, ypixels(-5.0)); // bottom right
    }
    glEnd();
    glBegin(GL_LINE_STRIP);
    {
        glVertex2f(xpixels(-5.0), 0.0); // bottom left
        glVertex2f(xpixels(-5.0), 1.0); // top left
    }
    glEnd();



    // Draw Tics X
    int numTicsX = getPropertyInt("Grid.Tics X");
    drawXTics(propertyX, numTicsX);


    // Draw Tics Y
    int numTicsY = getPropertyInt("Grid.Tics Y");
    drawYTics(propertyY, numTicsY);

    // X Y property label
    setFont(getCurrentFont(), getCurrentFontSize(), "bold");
    renderText(0.5, ypixels(-30.0), (char*) propertyX.c_str(), "center");
    renderText(0.0, 1.0 +  ypixels(2.0), (char*) propertyY.c_str());
    setFont(getCurrentFont(), getCurrentFontSize(), "normal");
    srand(1);

    updateColors(); //this update hover node
    renderPoints();

    if(hoveredNode!=0) {
        renderHoveredPoint(hoveredNode);
    }

    glPointSize(1);
}

void ScatterPlot::mouseMove(float px, float py, float dx, float dy, int modifiers) {
    View::mouseMove(px, py, dx, dy, modifiers);
    toView(px, py, mousex, mousey);
}

void ScatterPlot::update(string property){
    View::update(property);
	if((property=="Grid.Logscale X") || (property=="Grid.Logscale Y")) {
		updateVertices();
	}
}


/**
 * Edge Scatter plot
 **/
class NodeScatterPlot: public ScatterPlot {
public:
	NodeScatterPlot(const char *title): ScatterPlot(title, "node") {
	}
		
};


/**
 * Edge Scatter plot
 **/
class EdgeScatterPlot: public ScatterPlot {
public:
    int numEdges;
    EdgeScatterPlot(const char *title): ScatterPlot(title, "edge") {
    }

    virtual unsigned int numElements() {
        unsigned int k = 0;
        ElementIterator *it = createIterator();
        assert(it);
        for(it->begin();it->hasMoreElements();) {
            it->next();
            k++;
        }
				graph->destroyIterator(it);
        return k;
    }
    virtual ElementIterator * createIterator() {
        return graph->createEdgeIterator();
    }

};


View *createScatter(string name) {
    NodeScatterPlot *plot = new NodeScatterPlot(name.c_str());
    plot->setup();
    plot->setLocation(0.0, 1.0);
    plot->setSize(1.0, 0.7);
    plot->setVisible(true);
    plot->addSelectionWidget();
    plot->addCloseButton();
    plot->addTransparentButton();
    return plot;
}
View *createEdgeScatter(string name) {
    EdgeScatterPlot *plot = new EdgeScatterPlot(name.c_str());
    plot->setup();
    plot->setLocation(0.0, 1.0);
    plot->setSize(1.0, 0.7);
    plot->setVisible(true);
    plot->addSelectionWidget();
    plot->addCloseButton();
    plot->addTransparentButton();
    return plot;
}
